#include "maindialog.h"
#include "aboutdialog.h"

#include "keybdef.h"
#include "keybconf.h"
#include "keybicon.h"

#include "processwrapper.h"
#include "version.h"

#include <QtCore>
#include <QDebug>

// all the available layouts
InputLayouts layouts;

// current configuration
std::vector<KB_Struct> switchCases;

// icons
KB_Icon kb_icons;

////////////////////////////////////////////////////////////////////////////////

#define     KEY_ROLE    2000
#define     KLID_ROLE   2001
#define     INDEX_ROLE  2002

////////////////////////////////////////////////////////////////////////////////

static QImage qt_fromWinHBITMAP(HDC hdc, HBITMAP bitmap, int w, int h)
{
    BITMAPINFO bmi;
    memset(&bmi, 0, sizeof(bmi));
    bmi.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth       = w;
    bmi.bmiHeader.biHeight      = -h;
    bmi.bmiHeader.biPlanes      = 1;
    bmi.bmiHeader.biBitCount    = 32;
    bmi.bmiHeader.biCompression = BI_RGB;
    bmi.bmiHeader.biSizeImage   = w * h * 4;

    QImage image(w, h, QImage::Format_ARGB32_Premultiplied);
    if (image.isNull())
        return image;

    // Get bitmap bits
    uchar *data = (uchar *) qMalloc(bmi.bmiHeader.biSizeImage);

    if (GetDIBits(hdc, bitmap, 0, h, data, &bmi, DIB_RGB_COLORS)) {
        // Create image and copy data into image.
        for (int y=0; y<h; ++y) {
            void *dest = (void *) image.scanLine(y);
            void *src = data + y * image.bytesPerLine();
            memcpy(dest, src, image.bytesPerLine());
        }
    } else {
        qWarning("qt_fromWinHBITMAP(), failed to get bitmap bits");
    }
    qFree(data);

    return image;
}

QPixmap fromWinHICON(HICON icon)
{
   bool foundAlpha = false;
   HDC screenDevice = GetDC(0);
   HDC hdc = CreateCompatibleDC(screenDevice);
   ReleaseDC(0, screenDevice);

   ICONINFO iconinfo;
   bool result = GetIconInfo(icon, &iconinfo); //x and y Hotspot describes the icon center
   if (!result)
       qWarning("QPixmap::fromWinHICON(), failed to GetIconInfo()");

   int w = iconinfo.xHotspot * 2;
   int h = iconinfo.yHotspot * 2;

   BITMAPINFOHEADER bitmapInfo;
   bitmapInfo.biSize        = sizeof(BITMAPINFOHEADER);
   bitmapInfo.biWidth       = w;
   bitmapInfo.biHeight      = h;
   bitmapInfo.biPlanes      = 1;
   bitmapInfo.biBitCount    = 32;
   bitmapInfo.biCompression = BI_RGB;
   bitmapInfo.biSizeImage   = 0;
   bitmapInfo.biXPelsPerMeter = 0;
   bitmapInfo.biYPelsPerMeter = 0;
   bitmapInfo.biClrUsed       = 0;
   bitmapInfo.biClrImportant  = 0;
   DWORD* bits;

   HBITMAP winBitmap = CreateDIBSection(hdc, (BITMAPINFO*)&bitmapInfo, DIB_RGB_COLORS, (VOID**)&bits, NULL, 0);
   HGDIOBJ oldhdc = (HBITMAP)SelectObject(hdc, winBitmap);
   DrawIconEx( hdc, 0, 0, icon, iconinfo.xHotspot * 2, iconinfo.yHotspot * 2, 0, 0, DI_NORMAL);
   QImage image = qt_fromWinHBITMAP(hdc, winBitmap, w, h);

   for (int y = 0 ; y < h && !foundAlpha ; y++) {
       QRgb *scanLine= reinterpret_cast<QRgb *>(image.scanLine(y));
       for (int x = 0; x < w ; x++) {
           if (qAlpha(scanLine[x]) != 0) {
               foundAlpha = true;
               break;
           }
       }
   }
   if (!foundAlpha) {
       //If no alpha was found, we use the mask to set alpha values
       DrawIconEx( hdc, 0, 0, icon, w, h, 0, 0, DI_MASK);
       QImage mask = qt_fromWinHBITMAP(hdc, winBitmap, w, h);

       for (int y = 0 ; y < h ; y++){
           QRgb *scanlineImage = reinterpret_cast<QRgb *>(image.scanLine(y));
           QRgb *scanlineMask = mask.isNull() ? 0 : reinterpret_cast<QRgb *>(mask.scanLine(y));
           for (int x = 0; x < w ; x++){
               if (scanlineMask && qRed(scanlineMask[x]) != 0)
                   scanlineImage[x] = 0; //mask out this pixel
               else
                   scanlineImage[x] |= 0xff000000; // set the alpha channel to 255
           }
       }
   }
   //dispose resources created by iconinfo call
   DeleteObject(iconinfo.hbmMask);
   DeleteObject(iconinfo.hbmColor);

   SelectObject(hdc, oldhdc); //restore state
   DeleteObject(winBitmap);
   DeleteDC(hdc);
   return QPixmap::fromImage(image);
}

////////////////////////////////////////////////////////////////////////////////

MainDialog::MainDialog(QWidget *parent) :
    QDialog(parent){
    ui.setupUi(this);

    keysMenu = new QMenu(tr("Add new key"), this);
    keysMenu->setIcon(QIcon(":/gui/key_bindings.png"));
    ui.tbAddKeys->setMenu(keysMenu);
    connect(keysMenu, SIGNAL(triggered(QAction*)), this, SLOT(addKeyFromPopup(QAction*)));

    keysChangeMenu = new QMenu(tr("Change/Swap/Join with"), this);
    connect(keysChangeMenu, SIGNAL(triggered(QAction*)), this, SLOT(changeKeyFromPopup(QAction*)));

    layoutsMenu = new QMenu(tr("Add new layout"), this);
    layoutsMenu->setIcon(QIcon(":/gui/keyboard_layout.png"));
    ui.tbAddLayouts->setMenu(layoutsMenu);
    connect(layoutsMenu, SIGNAL(triggered(QAction*)), this, SLOT(addLayoutFromPopup(QAction*)));

    popupMenu = new QMenu(this);
    popupMenu->addMenu(keysMenu);
    popupMenu->addMenu(layoutsMenu);
    popupMenu->addSeparator();
    popupMenu->addMenu(keysChangeMenu);
    popupMenu->addSeparator();
    popupMenu->addAction(ui.actionDelete);

    ui.tbDel->setDefaultAction(ui.actionDelete);

    // setup title
    setWindowTitle(tr("KeySwitcher %1 Configurator").arg(version_txt));


    // load layouts
    initLayoutList(layouts);

    // load icons
    kb_icons.loadIcons(layouts);

    // fill layouts list
    for (int i = 0; i < layouts.size(); i++)
    {
        // popup menu
        QAction *act = layoutsMenu->addAction(
            ::fromWinHICON(kb_icons.HKL2HICON(layouts.at(i).hkl)),
            QString::fromWCharArray(layouts.at(i).name.c_str())
            );
        act->setData(i);  // set index
    }

    // fill keys list
    fillKeyCodes();

    // popup menus
    foreach (unsigned vk, keyCodes.keys())
    {
        QAction *act = keysMenu->addAction(
            keyCodes.value(vk)
            );
        act->setData(vk);  // set index


        QAction *act2 = keysChangeMenu->addAction(
            keyCodes.value(vk)
            );
        act2->setData(vk);  // set index
    }


    // read current config
    getConfiguration();

    // check if update has been finished
    if (qApp->arguments().count() >= 4)
      if (qApp->arguments().at(2) == "/i")
      {
        QTimer::singleShot(10, this, SLOT(showAfterUpdate()));
      }
}

void MainDialog::fillKeyCodes()
{
    keyCodes[VK_LWIN] = tr("Left Win");
    keyCodes[VK_RWIN] = tr("Right Win");
    keyCodes[VK_LSHIFT] = tr("Left Shift");
    keyCodes[VK_RSHIFT] = tr("Right Shift");
    keyCodes[VK_LCONTROL] = tr("Left Control");
    keyCodes[VK_RCONTROL] = tr("Right Control");
    keyCodes[VK_LMENU] = tr("Left WinMenu");
    keyCodes[VK_RMENU] = tr("Right WinMenu");

    keyCodes[VK_NUMLOCK] = tr("NumLock");
    keyCodes[VK_SCROLL] = tr("ScrollLock");
    keyCodes[VK_CAPITAL] = tr("CapsLock");
}

bool MainDialog::getConfiguration()
{
    // read keyboard configuration
    if (!readKeyboardConfig(switchCases))
    {
      // fill default config
      KB_Struct kbs;
      kbs.keyCode = VK_LCONTROL;
      for (int j = 0; j < layouts.size(); j++)
      {
        kbs.layouts.push_back(layouts.at(j).klid);
      }
    }

    // publish it
    for (int i = 0; i < switchCases.size(); i++)
    {
        const KB_Struct &kbs = switchCases.at(i);

        // insert key into list
        QTreeWidgetItem *wi = insertKey(kbs.keyCode);

        // layouts
        for (int j = 0; j < kbs.layouts.size(); j++)
        {
            const unsigned klid = kbs.layouts.at(j);

            //qDebug() << QString::fromStdWString(klid);

            insertLayout(wi, klid);
        }
    }

    ui.twConfig->expandAll();

    // read other config
    readLocalSettings();

    return true;
}

QTreeWidgetItem* MainDialog::insertLayout(QTreeWidgetItem *root, unsigned klid, int idx)
{
    // search layouts
    if (idx < 0)
    {
        for (int k = 0; k < layouts.size(); k++)
        {
            if (klid == layouts.at(k).klid)
            {
                idx = k;
                break;
            }
        }
    }

    // if found
    QString layout;
    if (idx >= 0)
    {
        layout = QString::fromStdWString(layouts.at(idx).name);
    }
    else
    {
        layout = tr("Layout %1").arg(klid);
    }

    QTreeWidgetItem *wl = new QTreeWidgetItem(root);
    wl->setText(1, layout);
    wl->setData(1, KLID_ROLE, klid);
    wl->setData(1, INDEX_ROLE, idx);
    wl->setIcon(1, ::fromWinHICON(kb_icons.HKL2HICON(layouts.at(idx).hkl)));
    return wl;
}

void MainDialog::on_tbUp_clicked()
{
    QTreeWidgetItem *wi = ui.twConfig->currentItem();
    if (!wi)
        return;

    QTreeWidgetItem *root = wi->parent();
    if (!root || root == ui.twConfig->invisibleRootItem())
        return;

    if (root->childCount() < 2)
        return;

    int idx = root->indexOfChild(wi);
    if (idx == 0)
        return;

    root->removeChild(wi);
    root->insertChild(idx-1, wi);

    ui.twConfig->setCurrentItem(wi);
}

void MainDialog::on_tbTop_clicked()
{
    QTreeWidgetItem *wi = ui.twConfig->currentItem();
    if (!wi)
        return;

    QTreeWidgetItem *root = wi->parent();
    if (!root || root == ui.twConfig->invisibleRootItem())
        return;

    if (root->childCount() < 2)
        return;

    int idx = root->indexOfChild(wi);
    if (idx == 0)
        return;

    root->removeChild(wi);
    root->insertChild(0, wi);

    ui.twConfig->setCurrentItem(wi);
}

void MainDialog::on_tbDown_clicked()
{
    QTreeWidgetItem *wi = ui.twConfig->currentItem();
    if (!wi)
        return;

    QTreeWidgetItem *root = wi->parent();
    if (!root || root == ui.twConfig->invisibleRootItem())
        return;

    if (root->childCount() < 2)
        return;

    int idx = root->indexOfChild(wi);
    if (idx == root->childCount()-1)
        return;

    root->removeChild(wi);
    root->insertChild(idx+1, wi);

    ui.twConfig->setCurrentItem(wi);
}

void MainDialog::on_tbBottom_clicked()
{
    QTreeWidgetItem *wi = ui.twConfig->currentItem();
    if (!wi)
        return;

    QTreeWidgetItem *root = wi->parent();
    if (!root || root == ui.twConfig->invisibleRootItem())
        return;

    if (root->childCount() < 2)
        return;

    int idx = root->indexOfChild(wi);
    if (idx == root->childCount()-1)
        return;

    root->removeChild(wi);
    root->insertChild(root->childCount(), wi);

    ui.twConfig->setCurrentItem(wi);
}

void MainDialog::on_actionDelete_triggered()
{
    QTreeWidgetItem *wi = ui.twConfig->currentItem();
    if (!wi)
        return;

    QTreeWidgetItem *root = wi->parent();
    if (!root)
    {
        qDeleteAll( wi->takeChildren() );
        ui.twConfig->takeTopLevelItem( ui.twConfig->indexOfTopLevelItem(wi) );
        delete wi;
    }
    else
    {
        root->removeChild(wi);
        delete wi;
    }

}

QTreeWidgetItem* MainDialog::currentKey()
{
  QTreeWidgetItem *wi = ui.twConfig->currentItem();
  if (!wi)
      return 0;

  QTreeWidgetItem *root = wi->parent();
  if (root) // not top level
      return root;

  return wi;
}

QTreeWidgetItem* MainDialog::findKeyItem(unsigned int vk)
{
  for (int i = 0; i < ui.twConfig->topLevelItemCount(); i++)
  {
      QTreeWidgetItem *wi = ui.twConfig->topLevelItem(i);
      if (wi->data(0, KEY_ROLE).toUInt() == vk)
      {
          return wi;
      }
  }
  return 0;
}

void MainDialog::addKey(unsigned vk)
{
  // check if such item presents in the list
  QTreeWidgetItem *wi = findKeyItem(vk);
  if (wi)
  {
      ui.twConfig->setCurrentItem(wi);
      wi->setExpanded(true);
      return;
  }

  // not found - insert it
  wi = insertKey(vk);
  ui.twConfig->setCurrentItem(wi);
}

QTreeWidgetItem* MainDialog::insertKey(unsigned vk)
{
    QString key;

    if (keyCodes.contains(vk))
        key = keyCodes[vk];
    else
        key = QString(tr("Key %1").arg(vk));

    QTreeWidgetItem *wi = new QTreeWidgetItem(ui.twConfig);
    wi->setText(0, key);
    wi->setData(0, KEY_ROLE, vk);
    return wi;
}

void MainDialog::addLayout(unsigned klid, int idx)
{
    QTreeWidgetItem *wi = currentKey();
    if (!wi)
        return;

    // check if layout present
    for (int i = 0; i < wi->childCount(); i++)
    {
        QTreeWidgetItem *wli = wi->child(i);
        // check KLID
        if (wli->data(1, KLID_ROLE).toUInt() == klid)
        {
            ui.twConfig->setCurrentItem(wli);
            return;
        }
    }

    // not found - insert it
    QTreeWidgetItem *wli = insertLayout(wi, klid, idx);
    ui.twConfig->setCurrentItem(wli);
}


void MainDialog::addKeyFromPopup(QAction *act)
{
  addKey(act->data().toUInt());
}

void MainDialog::changeKeyFromPopup(QAction *act)
{
  QTreeWidgetItem *wi = currentKey();
  if (!wi)
      return;

  // wi is the current key
  int id1 = wi->data(0, KEY_ROLE).toUInt();
  // id of target
  int id2 = act->data().toUInt();

  if (id1 == id2)
    return;

  // search for id2...
  QTreeWidgetItem *wi2 = findKeyItem(id2);
  if (wi2)
  {
    QString name1 = keyCodes[id1];
    QString name2 = keyCodes[id2];
    QMessageBox mb(QMessageBox::Question,
                  "Key assigned",
                   QString("The key %1 is configured already.<br>"
                   "What do you want to do?").arg(name2));
    mb.addButton(QString("Swap %1 and %2").arg(name1,name2), QMessageBox::YesRole);
    mb.addButton(QString("Join under %1").arg(name1), QMessageBox::NoRole);
    //mb.addButton(QString("Replace %1 with %2").arg(keyCodes[id1],keyCodes[id2]), QMessageBox::NoRole);
    mb.addButton(QMessageBox::Cancel);

    int res = mb.exec();
    if (res == QMessageBox::Cancel) return;

    QAbstractButton *btn = mb.clickedButton();

    // swap
    if (mb.buttonRole(btn) == QMessageBox::YesRole)
    {
      wi->setText(0, name2);
      wi->setData(0, KEY_ROLE, id2);
      wi2->setText(0, name1);
      wi2->setData(0, KEY_ROLE, id1);
      return;
    }

    // else join
    wi->addChildren(wi2->takeChildren());
    delete wi2;

    return;
  }

  // not found -> just change...
  wi->setText(0, act->text());
  wi->setData(0, KEY_ROLE, id2);
}

void MainDialog::addLayoutFromPopup(QAction *act)
{
  int idx = act->data().toInt();
  unsigned klid = layouts.at(idx).klid;

  addLayout(klid, idx);
}

void MainDialog::on_twConfig_customContextMenuRequested ( const QPoint & pos )
{
  popupMenu->popup(ui.twConfig->mapToGlobal(pos));
}



void MainDialog::on_pbOk_clicked()
{
  switchCases.clear();

  for (int i = 0; i < ui.twConfig->topLevelItemCount(); i++)
  {
      KB_Struct kbs;
      QTreeWidgetItem *wi = ui.twConfig->topLevelItem(i);
      kbs.keyCode = wi->data(0, KEY_ROLE).toUInt();
      for (int j = 0; j < wi->childCount(); j++)
      {
          QTreeWidgetItem *lwi = wi->child(j);
          unsigned klid = lwi->data(1, KLID_ROLE).toUInt();
          kbs.layouts.push_back(klid);
      }
      switchCases.push_back(kbs);
  }

  // store configuration
  writeKeyboardConfig(switchCases);

  // store local config
  writeLocalSettings();

  // restart keyswitch
  restartResident();
}





void MainDialog::restartResident()
{
  // find resident
  unsigned int pid = ProcessWrapper::findProcess("keyswitch");

  // if exists stop
  if (pid)
  {
    //ProcessWrapper::terminateProcess(pid);
    bool b = ProcessWrapper::sendMessage(pid, WM_DESTROY, 0, 0);
    if (!b)
      ProcessWrapper::terminateProcess(pid);
  }

  // start again
  on_pbStart_clicked();
}

void MainDialog::on_pbStart_clicked()
{
  // start
  QString app = qApp->applicationDirPath() + "/keyswitch.exe";
  QDir::setCurrent(qApp->applicationDirPath());
  QProcess::startDetached(app, QStringList(), qApp->applicationDirPath());
}

void MainDialog::on_pbStop_clicked()
{
  // find resident
  unsigned int pid = ProcessWrapper::findProcess("keyswitch");

  // if exists stop
  if (pid)
  {
    //ProcessWrapper::terminateProcess(pid);
    bool b = ProcessWrapper::sendMessage(pid, WM_DESTROY, 0, 0);
    if (!b)
      ProcessWrapper::terminateProcess(pid);
  }
}



void MainDialog::showAfterUpdate()
{
  on_pbStart_clicked();

  QString res = qApp->arguments().at(3);

  if (res == "/res:ok")
  {
    QMessageBox::information(this, "Update completed", "Update finished successfully");
    return;
  }

  if (res == "/res:unzip_failed")
  {
    QMessageBox::warning(this, "Update completed", "Update finished with some problems");
    return;
  }

  QMessageBox::critical(this, "Update failed", "Update failed with errors");
}

void MainDialog::on_pbUpdate_clicked()
{
  QString app = qApp->applicationDirPath() + "/updater.exe";
  QProcess::startDetached(app, QStringList(), qApp->applicationDirPath());
}

void MainDialog::on_pbAbout_clicked()
{
  AboutDialog(this).exec();
}

